//
//  vector3.cpp
//  GLDemo
//
//  Created by wneqi on 14-5-30.
//  Copyright (c) 2014年 xiewneqi. All rights reserved.
//

#include "vector3.h"

const Vector3 Vector3::back (0, 0, 1);

const Vector3 Vector3::forward (0, 0, -1);

const Vector3 Vector3::left (-1, 0, 0);

const Vector3 Vector3::right (1, 0, 0);

const Vector3 Vector3::down (0, -1, 0);

const Vector3 Vector3::up (0, 1, 0);

const Vector3 Vector3::one (1, 1, 1);

const Vector3 Vector3::zero (0, 0, 0);

Vector3::Vector3 ()
: m_x (0)
, m_y (0)
, m_z (0)
{
    // do nothing here
}

Vector3::Vector3 (GLfloat x, GLfloat y, GLfloat z)
: m_x (x)
, m_y (y)
, m_z (z)
{
    // do nothing here
}

Vector3::~Vector3 ()
{
    // do nothing here
}

GLfloat Vector3::GetMagnitude () const
{
    return sqrt (m_x * m_x + m_y * m_y + m_z *m_z);
}

Vector3 Vector3::GetNormalized () const
{
    GLfloat myMagnitude = GetMagnitude ();
    return (*this) / myMagnitude;
}

GLfloat Vector3::GetSqrMagnitude () const
{
    return m_x * m_x + m_y * m_y + m_z *m_z;
}

void Vector3::Normalize ()
{
    GLfloat myMagnitude = GetMagnitude ();
    GLfloat factor = 1/ myMagnitude;
    Set (m_x / factor, m_y / factor, m_z / factor);
}

void Vector3::Set (GLfloat x, GLfloat y, GLfloat z)
{
    m_x = x;
    m_y = y;
    m_z = z;
}

void Vector3::SetX (GLfloat x)
{
    m_x = x;
}

void Vector3::SetY (GLfloat y)
{
    m_y = y;
}

void Vector3::SetZ (GLfloat z)
{
    m_z = z;
}

GLfloat Vector3::GetX () const
{
    return m_x;
}

GLfloat Vector3::GetY () const
{
    return m_y;
}

GLfloat Vector3::GetZ () const
{
    return m_z;
}

GLfloat& Vector3::operator[] (int i)
{
    if (i == 0)
    {
        return m_x;
    }
    else if (i == 1)
    {
        return m_y;
    }
    else if (i == 2)
    {
        return m_z;
    }
    throw "Invalid index with is not in range from 0 to 2";
}

const GLfloat& Vector3::operator[] (int i) const
{
    if (i == 0)
    {
        return m_x;
    }
    else if (i == 1)
    {
        return m_y;
    }
    else if (i == 2)
    {
        return m_z;
    }
    throw "Invalid index with is not in range from 0 to 2";
}

Vector3 Vector3::operator - () const
{
    return Vector3 (-m_x, -m_y, -m_z);
}

Vector3 Vector3::operator + (const Vector3& v) const
{
    return Vector3 (m_x + v.GetX (), m_y + v.GetY (), m_z + v.GetZ ());
}

Vector3 Vector3::operator - (const Vector3& v) const
{
    return Vector3 (m_x - v.GetX (), m_y - v.GetY (), m_z - v.GetZ ());
}

Vector3 Vector3::operator * (GLfloat s) const
{
    return Vector3 (m_x * s, m_y * s, m_z * s);
}

Vector3 Vector3::operator / (GLfloat s) const
{
    if (s == 0)
    {
        throw "Divide zero exception";
    }
    return Vector3 (m_x / s, m_y / s, m_z / s);
}

bool Vector3::operator == (const Vector3& v) const
{
    return m_x == v.GetX () && m_y == v.GetY () && m_z == v.GetZ ();
}

bool Vector3::operator != (const Vector3& v) const
{
    return !(*this == v);
}

std::ostream& operator << (std::ostream& stream, const Vector3& v)
{
    return stream << "[ x = " << v.m_x << ", y = " << v.m_y << ", z = " << v.m_z << " ]";
}

GLfloat Vector3::AngleBetween (const Vector3& from, const Vector3& to)
{
    GLfloat fromMagnitude = from.GetMagnitude ();
    GLfloat toMagnitude = to.GetMagnitude ();
    if (fromMagnitude == 0 || toMagnitude == 0) return 0;
    float cosAngle = Dot (from, to) / (fromMagnitude * toMagnitude);
    return acos (cosAngle);
}

Vector3 Vector3::ClampMagnitude (const Vector3& v, GLfloat maxLength)
{
    GLfloat vMagnitude = v.GetMagnitude ();
    if (vMagnitude > maxLength)
    {
        return v / (maxLength / vMagnitude);
    }
    else
    {
        return v;
    }
}

Vector3 Vector3::Cross (const Vector3& left, const Vector3& right)
{
    return Vector3 (left.GetY () * right.GetZ () - left.GetZ () * right.GetY (),
                    left.GetZ () * right.GetX () - left.GetX () * right.GetZ (),
                    left.GetX () * right.GetY () - left.GetY () * right.GetX ());
}

GLfloat Vector3::Distance (const Vector3& from , const Vector3& to)
{
    Vector3 diff = from - to;
    return diff.GetMagnitude ();
}

GLfloat Vector3::Dot (const Vector3& left, const Vector3& right)
{
    return (left.m_x * right.m_x + left.m_y * right.m_y + left.m_z * right.m_z);
}

Vector3 Vector3::Lerp (const Vector3& left, const Vector3& right, GLfloat t)
{
    Vector3 diff = right - left;
    return left + diff * t;
}

Vector3 Vector3::Max (const Vector3& left, const Vector3& right)
{
    return Vector3 (fmax (left.GetX (), right.GetX ()), fmax (left.GetY (), right.GetY ()), fmax (left.GetZ (), right.GetZ ()));
}

Vector3 Vector3::Min (const Vector3& left, const Vector3& right)
{
    return Vector3 (fmin (left.GetX (), right.GetX ()), fmin (left.GetY (), right.GetY ()), fmin (left.GetZ (), right.GetZ ()));
}

Vector3 Vector3::MoveTowards (const Vector3& current, const Vector3& target, GLfloat maxDistanceDelta)
{
    Vector3 diff = target - current;
    if (maxDistanceDelta <= diff.GetMagnitude ())
    {
        return target;
    }
    else
    {
        return current + diff.GetNormalized () * maxDistanceDelta;
    }
}

void Vector3::OrthoNormalize (Vector3& normal, Vector3& tagent)
{
    normal.Normalize ();
    tagent.Normalize ();
    Vector3 thirdV = Vector3::Cross (normal, tagent);
    Vector3 result = Vector3::Cross (thirdV, normal);
    tagent.Set (result.GetX (), result.GetY (), result.GetZ ());
}

Vector3 Vector3::Project (const Vector3& vector, const Vector3& onNormal)
{
    Vector3 normal = onNormal.GetNormalized ();
    GLfloat magnitude = Vector3::Dot (vector, normal);
    return normal * magnitude;
}

Vector3 Vector3::Reflect (const Vector3& inDirection, const Vector3& inNormal)
{
    Vector3 inDirNormal = -inDirection.GetNormalized ();
    Vector3 _inNormal = inNormal.GetNormalized ();
    return _inNormal * (Vector3::Dot (inDirNormal, _inNormal)) * 2 - inDirNormal;
}

Vector3 Vector3::RotateTowards (const Vector3& current, const Vector3& target, GLfloat maxRadiansDelta, GLfloat maxMagnitudeDelta)
{
    // TODO xiewneqi 要使用四元数
    return Vector3 (0, 0, 0);
}

Vector3 Vector3::Scale (const Vector3& a, const Vector3& b)
{
    return Vector3 (a.GetX () * b.GetX (), a.GetY () * b.GetY (), a.GetZ () * b.GetZ ());
}

Vector3 Vector3::Slerp (const Vector3& from, const Vector3&to, GLfloat t)
{
    // TODO xiewneqi 要使用四元数
    return Vector3 (0, 0, 0);
}